Skip to content

feat(pr-review): server inbox, honest recents, and stale-submit recovery (w5a) - #5362

Merged
iscekic merged 17 commits into
mainfrom
audit-w5a-pr-review-ae21
Aug 19, 2026
Merged

feat(pr-review): server inbox, honest recents, and stale-submit recovery (w5a)#5362
iscekic merged 17 commits into
mainfrom
audit-w5a-pr-review-ae21

Conversation

@iscekic

@iscekic iscekic commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • The PR Review home now lists pull requests that are waiting for your review, loaded from the server with paging. You can still open any pull request by pasting its link.
  • If a pull request changes after you queue comments, the outdated comments are marked and stay in your queue so you can edit or remove them.
  • When you submit a review, only the comments that still match the current pull request are sent. Outdated comments stay in your queue and are not sent.
  • The file navigator now loads files as you scroll instead of loading every file up front, so large pull requests open faster.
  • The organization Members list now scrolls smoothly for large organizations.
  • Recent pull requests are saved only after they open successfully. A failed entry shows a Retry button, and every entry has a Remove button.
  • Diff context, file content, and viewed-file state are cached during review, so revisiting files is faster.
  • The pull request overview and its checks now load their parts at the same time, so the screen appears sooner.

Reviewer Notes

The PR review entry screen now shows a server-backed inbox of pull requests requesting the viewer's review, paginated through a new listInbox query. The search string is fixed server-side, so a caller can only ever see pull requests their own GitHub token can already see. Direct URL open is unchanged, and pagination is capped at 20 pages to stay inside GraphQL search rate limits.

Files
  • apps/web/src/lib/github-pr-review/mappers.ts — adds buildInboxResult and the GraphQlInboxNode type; drops non-PR search nodes.
  • apps/web/src/lib/github-pr-review/dtos.ts — adds the inbox item and result schemas plus INBOX_PAGE_SIZE.
  • apps/mobile/src/lib/pr-review/use-pr-inbox.ts — infinite-query hook; 20-page cap and 30-second stale time.
  • apps/mobile/src/components/pr-review/pr-review-inbox-list.tsx — FlashList body composing the paste header, inbox rows, and recents footer.
  • apps/mobile/src/components/pr-review/pr-review-inbox-view.ts — pure state selector for the seven inbox states.
  • apps/mobile/src/components/pr-review/pr-review-entry-screen.tsx — mounts the inbox list; renders the recents Retry and Remove buttons.
  • apps/mobile/src/components/ui/icons.ts — adds the Inbox icon.

The overview and check queries now start their sub-requests in parallel and settle them with Promise.allSettled, rethrowing the first rejection in leg order. This removes the sequential round-trips that previously serialized the overview's REST and GraphQL legs and the checks' two paginations.

Files
  • apps/web/src/routers/github-pr-review-router.ts — adds listInbox, INBOX_QUERY, and INBOX_SEARCH_QUERY; parallelizes getPullRequest and listChecks.

The submit sheet now partitions queued comments into fresh and stale by comparing each comment's recorded commit SHA against the current head. Only fresh comments are sent in the batched submit; stale comments stay queued, are flagged outdated, and the sheet reports a partial result instead of dismissing. This replaces the previous behavior of submitting everything against the latest head and surfacing per-item 422s.

Files
  • apps/mobile/src/lib/pr-review/partition-pending-items.ts — splits the queue into fresh and stale; an empty head makes every item fresh.
  • apps/mobile/src/components/pr-review/pr-review-submit.tsx — submits fresh items only, removes them via removeComments, and shows the partial result.
  • apps/mobile/src/components/pr-review/pr-review-submit-view.ts — CTA label and partial-result copy selectors.
  • apps/mobile/src/components/pr-review/pr-review-pending-comment-row.tsx — stale flag, Outdated badge, and updated hint copy.
  • apps/mobile/src/lib/pr-review/build-submit-review-input.ts — documents the fresh-only submission contract.
  • apps/mobile/src/lib/pr-review/pending-review-provider.tsx — adds removeComments with the hydration-generation guard.

Recent PRs now carry a last-load outcome and are written only after the first authorized payload loads; a failed open marks an existing entry failed rather than creating one. The entry screen shows a Retry button for failed entries and a Remove button for every entry.

Files
  • apps/mobile/src/lib/pr-review/recent-prs.ts — adds lastResult, removeRecentPr, and markRecentPrFailed.
  • apps/mobile/src/components/pr-review/pr-review-screen.tsx — backfills with lastResult: 'ok'; marks an existing entry failed on error.
  • apps/mobile/src/components/pr-review/recent-pr-row-state.ts — row-state selector for title, identity, and failed marker.

The file navigator now renders file rows in a virtualized list and loads pages on scroll instead of fetching the full file set on mount. An active search still drives fetch-to-completion so filtering covers the full listed set. The memoized rows read the latest select and toggle handlers through refs, so the rows stay identity-stable across a search re-render yet never go stale when a head-SHA change swaps the toggle handler.

Files
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx — FlashList with onEndReached and memoized rows; per-path callbacks read the latest handlers through refs.
  • apps/mobile/src/lib/pr-review/diff/navigator-file-filter.ts — extracted path filter and load-all decision.

The organization Members screen now renders a single virtualized list built from a pure item builder, with loading, error, and empty states selected by a dedicated error-view selector. Invited members now render alongside active members in the same list.

Files
  • apps/mobile/src/components/organization/members-screen.tsx — FlashList with section, member, and invite items.
  • apps/mobile/src/components/organization/members-list-items.ts — item builder with exact composition rules.
  • apps/mobile/src/components/organization/org-list-error-view.ts — error-view selector; no reconnect state.

Diff context expansion now fetches through the React Query cache with a 5-minute stale time and 10-minute GC time instead of a raw tRPC client call, so repeated context loads reuse cached lines. Viewed-file state adds a process-lifetime cache with a generation guard; when a read observes a generation change, it returns the authoritative current map rather than the stale parse, so a toggle or clear never races a re-read.

Files
  • apps/mobile/src/lib/pr-review/diff/use-pr-diff-context-loader.tsqueryClient.fetchQuery with stale and GC times.
  • apps/mobile/src/lib/pr-review/diff/context-window.ts — extracted window arithmetic.
  • apps/mobile/src/lib/pr-review/viewed-files.ts — cached map plus a generation guard; the guard now protects the caller's return value, not only the cache.

The Discussion tab's state selection is extracted into a pure selectDiscussionTabView function so the seven outcomes can be unit-tested. The tab renders exactly what it rendered before; only the decision is moved.

Files
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab-view.ts — pure state selector for the seven tab states.
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab.tsx — uses the selector instead of inline branches.

Satisfied on main, not reassigned and not reimplemented: P1-F-45, P2-GH-52d, P1-F-46c, P2-E-30a, P2-E-33b.

Two assumptions were corrected during the build:

  1. The server does not fence submitReview on expectedHeadSha, and it does not return stale_head in the review path; stale_head exists only in the merge reconcile path. Stale handling is solved entirely on the client.
  2. The highlight module already has a bounded content-addressed LRU cache (5,000 entries, keyed by language and line text). No highlight change was made.

Tests: 20 test files added or updated.
Generated: none.


Verification

  • No manual test paths were recorded for this change; verification relies on the automated test suite added in this branch.

Visual Changes

PR Review entry screen with the server-backed inbox

entry-screen.png

Submit sheet showing an outdated comment kept in the queue

now.png

Human steps: none known.

Notes: none.

Add , a server-fixed GraphQL search over review-requested
open PRs, so the mobile inbox reads only PRs the caller's token can see.
 drops non-PR and identity-less nodes instead of
throwing, and the schema guard now validates the inbox query.
Start the pulls, repos, and GraphQL legs of getPullRequest, and the two
paginate legs of listChecks, before awaiting. Await with Promise.allSettled
and rethrow the first rejection in leg order, so a second failure never
becomes an unhandled rejection. Authorization stays inside the token retry.
Route context expansion through the react-query cache so re-expanding a gap
is a hit, and hold the parsed viewed-files map in a process cache that a
generation guard keeps from being overwritten by a late first read. Sign-out
still drops the private content.
Replace the entry screen's ScrollView with a single FlashList whose header
and footer stay mounted in every inbox state, so direct URL open and Recents
survive an inbox failure. Recents now write only after the first authorized
payload, and failed entries expose Retry and Remove.
Partition queued comments by head SHA and send only the fresh ones, so a
stale item never blocks the valid ones. Stale items stay queued with an
Outdated badge and edit/delete, and removeComments carries the hydration
guard so an in-flight loadDraft cannot re-add just-posted comments.
Replace the ScrollView with a FlashList and page on scroll, so a large PR
does not mount every file row. A search still fetches the full listed set,
and a failed next page shows a retry CTA.
Replace the members ScrollView with a FlashList and drive it from a pure
item builder, with loading/error/empty evaluated in that order so an org
error never renders No members yet. Extract the discussion tab's state
selection into a pure, tested selector.
Use the repo's deferred-promise pattern (undefined as unknown as) and
mockReturnValueOnce so the two late-read tests pass oxlint.
The mapper receives only fresh items; stale items are never sent.
@iscekic iscekic self-assigned this Aug 19, 2026
Comment thread apps/mobile/src/lib/pr-review/viewed-files.ts
Comment thread apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the incremental commits since f30037c (empty-approve no-op, fetch-to-completion error routing, viewed-file clear fence, and anti-slop type/lint follow-ups); previous reviewer findings are fixed and no new high-confidence issues remain.

Files Reviewed (14 files)
  • apps/mobile/src/components/organization/org-list-error-view.ts
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.test.tsx
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab-view.ts
  • apps/mobile/src/lib/notification-path.ts
  • apps/mobile/src/lib/pr-review/diff/context-window.ts
  • apps/mobile/src/lib/pr-review/diff/pr-review-file-list-state.ts
  • apps/mobile/src/lib/pr-review/diff/use-fetch-to-completion.mounted.test.tsx
  • apps/mobile/src/lib/pr-review/diff/use-pr-diff-context-loader.ts
  • apps/mobile/src/lib/pr-review/partition-pending-items.ts
  • apps/mobile/src/lib/pr-review/pending-review-provider.mounted.test.tsx
  • apps/mobile/src/lib/pr-review/pending-review-provider.tsx
  • apps/mobile/src/lib/pr-review/recent-prs.ts
  • apps/mobile/src/lib/pr-review/viewed-files.test.ts
  • apps/mobile/src/lib/pr-review/viewed-files.ts
Previous Review Summaries (3 snapshots, latest commit f30037c)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f30037c)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx
  • apps/mobile/src/lib/pr-review/viewed-files.test.ts
  • apps/mobile/src/lib/pr-review/viewed-files.ts

Previous review (commit c48dcae)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/pr-review/viewed-files.ts 102 Generation guard still returns a stale parse to the caller
apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx 161 Cached row callbacks never update when handlers change
Files Reviewed (5 files)
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx - 1 issue
  • apps/mobile/src/components/pr-review/pr-review-inbox-view.ts
  • apps/mobile/src/lib/pr-review/diff/navigator-file-filter.test.ts
  • apps/mobile/src/lib/pr-review/diff/navigator-file-filter.ts
  • apps/mobile/src/lib/pr-review/viewed-files.test.ts

Fix these issues in Kilo Cloud

Previous review (commit dddb570)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/pr-review/viewed-files.ts 102 Generation guard still returns a stale parse to the caller
apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx 164 Cached row callbacks never update when handlers change
Files Reviewed (47 files)
  • apps/mobile/src/components/organization/members-list-items.test.ts
  • apps/mobile/src/components/organization/members-list-items.ts
  • apps/mobile/src/components/organization/members-screen.mounted.test.tsx
  • apps/mobile/src/components/organization/members-screen.tsx
  • apps/mobile/src/components/organization/org-list-error-view.test.ts
  • apps/mobile/src/components/organization/org-list-error-view.ts
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-navigator.tsx - 1 issue
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab-view.test.ts
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab-view.ts
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab.tsx
  • apps/mobile/src/components/pr-review/pr-review-entry-screen.tsx
  • apps/mobile/src/components/pr-review/pr-review-inbox-list.tsx
  • apps/mobile/src/components/pr-review/pr-review-inbox-view.test.ts
  • apps/mobile/src/components/pr-review/pr-review-inbox-view.ts
  • apps/mobile/src/components/pr-review/pr-review-pending-comment-row.tsx
  • apps/mobile/src/components/pr-review/pr-review-screen.tsx
  • apps/mobile/src/components/pr-review/pr-review-submit-view.test.ts
  • apps/mobile/src/components/pr-review/pr-review-submit-view.ts
  • apps/mobile/src/components/pr-review/pr-review-submit.test.tsx
  • apps/mobile/src/components/pr-review/pr-review-submit.tsx
  • apps/mobile/src/components/pr-review/recent-pr-row-state.test.ts
  • apps/mobile/src/components/pr-review/recent-pr-row-state.ts
  • apps/mobile/src/components/ui/icons.ts
  • apps/mobile/src/lib/pr-review/build-submit-review-input.test.ts
  • apps/mobile/src/lib/pr-review/build-submit-review-input.ts
  • apps/mobile/src/lib/pr-review/diff/context-window.test.ts
  • apps/mobile/src/lib/pr-review/diff/context-window.ts
  • apps/mobile/src/lib/pr-review/diff/navigator-file-filter.test.ts
  • apps/mobile/src/lib/pr-review/diff/navigator-file-filter.ts
  • apps/mobile/src/lib/pr-review/diff/use-pr-diff-context-loader.mounted.test.tsx
  • apps/mobile/src/lib/pr-review/diff/use-pr-diff-context-loader.ts
  • apps/mobile/src/lib/pr-review/partition-pending-items.test.ts
  • apps/mobile/src/lib/pr-review/partition-pending-items.ts
  • apps/mobile/src/lib/pr-review/pending-review-provider.mounted.test.tsx
  • apps/mobile/src/lib/pr-review/pending-review-provider.tsx
  • apps/mobile/src/lib/pr-review/recent-prs.test.ts
  • apps/mobile/src/lib/pr-review/recent-prs.ts
  • apps/mobile/src/lib/pr-review/use-pr-inbox.test.ts
  • apps/mobile/src/lib/pr-review/use-pr-inbox.ts
  • apps/mobile/src/lib/pr-review/viewed-files.test.ts
  • apps/mobile/src/lib/pr-review/viewed-files.ts - 1 issue
  • apps/web/src/lib/github-pr-review/dtos.ts
  • apps/web/src/lib/github-pr-review/mappers.ts
  • apps/web/src/routers/github-pr-review-graphql-schema.test.ts
  • apps/web/src/routers/github-pr-review-router.test.ts
  • apps/web/src/routers/github-pr-review-router.ts

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 276.1K · Output: 21.1K · Cached: 717.6K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 19, 2026
@iscekic iscekic changed the title feat(pr-review): server inbox, honest recents, and stale-submit recovery feat(pr-review): server inbox, honest recents, and stale-submit recovery (w5c) Aug 19, 2026
@iscekic iscekic changed the title feat(pr-review): server inbox, honest recents, and stale-submit recovery (w5c) feat(pr-review): server inbox, honest recents, and stale-submit recovery (w5a) Aug 19, 2026
@iscekic
iscekic requested a review from eshurakov August 19, 2026 13:57

@pandemicsyn pandemicsyn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, couple bot notes inline and some merge conflicts but preapproved

Comment thread apps/mobile/src/lib/pr-review/pending-review-provider.tsx
Comment thread apps/mobile/src/lib/pr-review/viewed-files.ts Outdated
# Conflicts:
#	apps/mobile/src/lib/pr-review/diff/use-pr-diff-context-loader.ts
#	apps/mobile/src/lib/pr-review/pending-review-provider.tsx
#	apps/mobile/src/lib/pr-review/recent-prs.ts
Empty removeComments is a no-op so APPROVE before hydration cannot
clear a persisted draft. Fetch-to-completion now surfaces a resolved
page error so the search retry can render. clearViewedFiles fences
the cache before deletion so a concurrent read never returns the
prior account.
Anti-slop now rejects anonymous object return types, so the discussion,
org-list, context-window, and partition selectors use named contracts.
toNotificationHref casts the built path into the typed-route union so
kilo-app typecheck passes after the main merge.
…s as strings

check-unused rejected the unused DiscussionTabViewKind export. kilo-app
lint rejected the NotificationHref cast because it did not change the
type. Return the built path as a string instead.
@iscekic
iscekic merged commit 7ad4b3e into main Aug 19, 2026
18 checks passed
@iscekic
iscekic deleted the audit-w5a-pr-review-ae21 branch August 19, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants